
#Rem
Header: AGFX Framework - Primitives
#End

Import mojo
Import agfx

#Rem
summary: TPrimitive
#End
Class TPrimitive
	
	Private
	Field type:Int = PRIMITIVE_TYPE_BASE
	Field _rotate_center_point:TVector2
	
	Public

	#Rem
	summary: bounce box of primitive
	#End	
	Field bbox:TBBox
	
	#Rem
	summary: list of points
	#End	
	Field points:ArrayList<TVector2>
	
	#Rem
	summary: edge list 
	edge list is generated only for line and above type
	#End	
	Field edges:ArrayList<TEdge>	 
	
	#Rem
	summary: calculated mass center
	#End	
	Field center:TVector2
	
	#Rem
	summary: actual angle 	
	#End	
	Field angle:Float 

	#Rem
	summary: constructor
	#End	
	Method New(t:Int)
		Self.type 					= t
		Self.points 				= New ArrayList<TVector2>()
		Self.edges					= New ArrayList<TEdge>	
		Self.center					= New TVector2()
		Self._rotate_center_point 	= New TVector2()
		Self.angle					= 0.0
		Self.bbox					= New TBBox()
	End Method
	
	#Rem
	summary: calc edges from points
	#End	
	Method CalcEdges:Void()
		
		If Self.type<>PRIMITIVE_TYPE_POINT Then
			Local line:TEdge
			
			Self.edges = New ArrayList<TEdge>	
			
			For Local i:Int = 0 To  Self.points.Size()-2
				line = New TEdge()
				line.A.Set(Self.points.Get(i))
				line.B.Set(Self.points.Get(i+1))
				line.Update()
				Self.edges.AddLast(line);		
			Next
			
			If Self.type<>PRIMITIVE_TYPE_POLYLINE Then
				line = New TEdge()
				line.A.Set(Self.points.GetLast())
				line.B.Set(Self.points.GetFirst())
				line.Update()
				Self.edges.AddLast(line);			
			End If
		EndIf
		
	End Method
	
	#Rem
	summary: add point to list
	#End	
	Method AddPoint:Void(p:TVector2)	
		If Self.CheckType() Then Self.points.Add(p)		
	End Method
	
	#Rem
	summary: set point ID to vector
	#End	
	Method SetPoint:Void(id:Int,p:TVector2)
		If Self.CheckType() Then Self.points.Set(id,p)
	End Method
	
	#Rem
	summary: get point ID
	#End	
	Method GetPoint:TVector2(id:Int)
		Return Self.points.Get(id)
	End Method
	
	#Rem
	summary: get points count
	#End	
	Method Count:Int()
		Return Self.points.Size()
	End Method
	
	#Rem
	summary: Get all points as ArrayList 
	#End	
	Method GetPoints:ArrayList<TVector2>()
		Return Self.points
	End Method
	
	#Rem
	summary: Get all edges as ArrayList
	#End	
	Method GetEdges:ArrayList<TVector2>()
		Return Self.edges
	End Method
	
	#Rem
	summary: change primitive position from vector
	#End	
	Method SetOffset:Void(o:TVector2)
		For Local p:TVector2 = EachIn Self.points
			p.Add(o)
		Next			
	End Method
	
	#Rem
	summary: summary: change primitive position from delta X,Y
	#End	
	Method SetOffset:Void(x:Float,y:Float)		
		For Local p:TVector2 = EachIn Self.points
			p.Add(x,y)
		Next				
	End Method
		
	#Rem
	summary: Set rotation center
	Note: This point isnt same like mass center point
	#End	
	Method SetRotationPoint:Void(c:TVector2)
		Self._rotate_center_point = New TVector2(c)
	End Method
	
	#Rem
	summary: rotate primitive around point defined by ID
	#End	
	Method RotateByID:Void(id:Int,da:Float)
		
		Self.SetRotationPoint(Self.GetPoint(id))
		Self._Rotate(da)		
		
	End Method
	
	#Rem
	summary: rotate primitive around mass center
	#End	
	Method Rotate:Void(da:Float)		
		Self.SetRotationPoint(Self.center)
		Self._Rotate(da)		
	End Method
	
	#Rem
	summary: rotate primitive aroud user defined center
	Note: cenetr point is stored to separate center point
	#End	
	Method RotateAt:Void(c:TVector2,da:Float)		
		Self.SetRotationPoint(c)
		Self._Rotate(da)		
		
	End Method
	
	#Rem
	summary: direct set of primitive angle
	#End	
	Method SetAngle:Void(ang:Float)
		Self.angle = ang
	End Method
		
	#Rem
	summary: project primitive to axis
	used for collsiion check
	#End	
	Method ProjectToAxis:Void( Axis : TVector2, MinV : Float[], MaxV : Float[] )
	
		Local DotP : Float =  VecDot( Axis, Self.points.GetFirst() )
		
		MinV[ 0 ] = DotP
		MaxV[ 0 ] = DotP
		
		Local i : Int
		
		For Local p:TVector2 = EachIn points
		
			DotP =  VecDot( Axis, p )

			MinV[ 0 ] = Min( DotP, MinV[ 0 ])

			MaxV[ 0 ] = Max( DotP, MaxV[ 0 ])

		Next
	End Method
	
	#Rem
	summary: calc bounce box
	#End	
	Method CalcBoundingBox:Void()
	
		Local bbox:Float[] = New Float[4]
		
		bbox[0] = $FFFFFFF
		bbox[1] = $FFFFFFF
		bbox[2] = 0
		bbox[3] = 0
		
		For Local p:TVector2 = EachIn Self.points
		
			bbox[0] = Min( bbox[0], p.x)
			bbox[1] = Min( bbox[1], p.y)
			bbox[2] = Max( bbox[2], p.x)
			bbox[3] = Max( bbox[3], p.y)
		Next
		
		Self.bbox.Min.Set(bbox[0],bbox[1])
		Self.bbox.Max.Set(bbox[2],bbox[3])
		Self.bbox.size.x = Abs(Self.bbox.Max.x-Self.bbox.Min.x)
		Self.bbox.size.y = Abs(Self.bbox.Max.y-Self.bbox.Min.y)
		Self.bbox.size_half = VecMult(Self.bbox.size,0.5)
		Self.bbox.radius = Self.bbox.size_half.x
		
	End Method
	
	#Rem
	summary: calc center of mass
	#End	
	Method CalcCenter:TVector2()
		
		Self.center.Set(0,0)		
		For Local p:TVector2 = EachIn Self.points
			Self.center.Add(p)			
		Next				
		Self.center.Mult(1.0/Self.points.Size())		
		Return Self.center
			
	End Method
	
	#Rem
	summary: return points as 1D array
	good for use with collision math
	#End	
	Method GetAsArray:Float[]()
		Local ap:Float[] = New Float[Self.Count()*2]
		Local i:Int=0
		For Local p:TVector2 = EachIn Self.points
			ap[i] = p.x
			ap[i+1] = p.y
			i+=2
		Next
		Return ap
	End Method
	
	#Rem
	summary: draw debug information - POINTS
	#End	
	Method DebugDraw_Points:Void()
	
		SetColor(0,255,0)
		For Local p:TVector2 = EachIn Self.points	
			DrawOval(p.x-2,p.y-2,4,4)
		Next
	
	End Method
	
	#Rem
	summary: draw debug information - USER ROTATION CENTER
	#End	
	Method DebugDraw_RotationCenter:Void()
		
		SetAlpha(1)
		SetColor (255,255,255)		
		
		Local cp:TVector2 = Self._rotate_center_point
		
		If cp Then
			DrawLine(cp.x-4,cp.y,cp.x+4,cp.y)
			DrawLine(cp.x,cp.y-4,cp.x,cp.y+4)
		EndIf
	End Method
	
	#Rem
	summary: draw debug information - MASS CENTER
	#End	
	Method DebugDraw_Center:Void()
		' draw center
		SetColor(255,0,0)
		DrawOval(Self.center.x-2,Self.center.y-2,4,4)
	End Method
	
	#Rem
	summary: draw debug information - EDGES
	#End	
	Method DebugDraw_Edges:Void()
		' Draw edges line		
		SetColor(0,255,0)
		For Local e:TEdge =  EachIn Self.edges
			e.DebugDraw_Edge()
			e.DebugDraw_EdgeCenter()
			e.DebugDraw_EdgeNormal()
		Next
	End Method
	
	#Rem
	summary: draw debug information at once
	#End	
	Method DrawDebug:Void()
	
		' draw edges
		Self.DebugDraw_Edges()
		
		' draw points
		Self.DebugDraw_Points()
		
		' rotation cenetr
		Self.DebugDraw_RotationCenter()
		
		' primitive mass cenetr
		Self.DebugDraw_Center()
		
		' BBOX corners		
		Self.bbox.DebugDraw_Corners()
	
		SetAlpha(1.0)	
		SetColor (255,255,255)
		
	End Method
	
	#Rem
	summary: Draw primitive as polygon
	#End	
	Method Draw:Void()
		Local vertices:Float[] = New Float[Self.points.Size()*2]
		Local i:Int = 0
		For Local p:TVector2 = EachIn  Self.points
			vertices[i] = p.x
			vertices[i+1] = p.y
			i += 2
		Next
			
		' draw polygon			
		DrawPoly(vertices)
			
	End Method
	
	Private
	
	Method _Rotate:Void(angle:Float)
		
		Local p:TVector2
		Self.angle += angle
		For p = EachIn Self.points
			p.Sub(Self._rotate_center_point)
			p.Set(VecRotate(p,angle))
			p.Add(Self._rotate_center_point)
		Next
		
	End Method
	
	Method CheckType:Bool()
		Local res:Bool = False
		Local err_txt:String
		
		Select Self.type
			Case PRIMITIVE_TYPE_POINT
				If Self.points.Size() < 1 Then res = True
				err_txt = "TPoint: You can't add more points to this primitive"
			Case PRIMITIVE_TYPE_LINE
				If Self.points.Size() < 2 Then res = True
				err_txt = "TLine: You can't add more points to this primitive"
			Case PRIMITIVE_TYPE_TRIANGLE
				If Self.points.Size() < 3 Then res = True
				err_txt = "TTriangle: You can't add more points to this primitive"
			Case PRIMITIVE_TYPE_RECTANGLE
				If Self.points.Size() < 4 Then res = True
				err_txt = "TRectangle: You can't add more points to this primitive"
			Case PRIMITIVE_TYPE_CIRCLE
				If Self.points.Size() < 1 Then res = True
				err_txt = "TCircle: You can't add more points to this primitive"
			Case PRIMITIVE_TYPE_POLYLINE
				res = True
			Case PRIMITIVE_TYPE_POLYGON
				res = True
			Case PRIMITIVE_TYPE_NGON
				res = True
		End Select	
		
		If res=False Then Error(err_txt)
		
		Return res
	End Method
	
	Method Update:Void(recalc_center:Bool=True,recalc_bbox:Bool=True,recalc_edge:Bool=True) Abstract
	
End Class